﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Collections;

namespace InfoPathTranslate
{
    enum DomNodeType
    {
        Condition,
        Text,
        Var,
        SetAttribute,
        General,
        Html
    }

    interface DomNode
    {
        DomNodeType GetNodeType();
        String EvalueText(InfoPathData data, XmlNode domXmlNode, XsltActionMap action);
        bool GenerateConditionJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action);
        String GenerateEvalJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action);
    }

    class DomConditionNode : DomNode
    {
        InfoPathExpression exp;
        DomNode trueNode;
        DomNode falseNode;
        String conditionId = null;

        public DomNodeType GetNodeType()
        {
            return DomNodeType.Condition;
        }

        public String EvalueText(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            String rst = null;

            if (this.exp.Test(data))
            {
                if (this.trueNode != null)
                {
                    rst = trueNode.EvalueText(data, domXmlNode, action);
                }
            }
            else
            {
                if (this.falseNode != null)
                {
                    rst = falseNode.EvalueText(data, domXmlNode, action);
                }
            }
            if (rst == null)
            {
                rst = "";
            }
            return rst;
        }

        public bool GenerateConditionJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return true;
        }

        public String GenerateEvalJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            if (this.conditionId != null)
            {
                return this.conditionId + "()";
            }
            ArrayList refs = new ArrayList();
            String js = this.exp.GetExpJs(data, refs);

            String trueJs = null;
            if (this.trueNode != null)
            {
                trueJs = this.trueNode.GenerateEvalJs(data, domXmlNode, action);
            }
            String falseJs = null;
            if (this.falseNode != null)
            {
                falseJs = this.falseNode.GenerateEvalJs(data, domXmlNode, action);
            }

            StringBuilder evalueJs = new StringBuilder();
            evalueJs.Append("if(" + js + ")\r\n{\r\n");
            if (trueJs != null)
            {
                evalueJs.Append("return ").Append(trueJs);
            }
            evalueJs.Append(";\r\n}\r\n");
            if (falseJs != null)
            {
                evalueJs.Append("else\r\n{\r\n").Append("return ").Append(falseJs).Append(";\r\n}\r\n");
            }

            String domId = InfoPathXml.GetNodeAttr(domXmlNode, "domId");
            if (domId == null)
            {
                domId = action.GenerateDomId();
                InfoPathXml.SetNodeAttr(domXmlNode, "domId", domId);
            }

            String condId = action.GenerateConditionId();
            action.AddConditionJs(condId, evalueJs.ToString());
            this.conditionId = condId;

            for (int i = 0; i < refs.Count; i++)
            {
                String refName = (String)refs[i];
                action.AddOnChangeRelation(refName, domId);
            }

            return this.conditionId + "()";

        }


        public void SetNodes(DomNode t, DomNode f)
        {
            this.trueNode = t;
            this.falseNode = f;
        }

        public void SetExp(InfoPathExpression e)
        {
            this.exp = e;
        }

        public void SetTrueNode(DomNode n)
        {
            this.trueNode = n;
        }

        public DomNode GetTrueNode()
        {
            return this.trueNode;
        }

        public void SetFalseNode(DomNode n)
        {
            this.falseNode = n;
        }

        public DomNode GetFalseNode()
        {
            return this.falseNode;
        }
    }

    class DomGeneralNode : DomNode
    {
        ArrayList subNodes = new ArrayList();
        public DomNodeType GetNodeType()
        {
            return DomNodeType.General;
        }

        public String EvalueText(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            String text = "";
            for (int i = 0; i < this.subNodes.Count; i++)
            {
                text += ((DomNode)this.subNodes[i]).EvalueText(data, domXmlNode, action);
            }
            return text;
        }

        public bool GenerateConditionJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            for (int i = 0; i < this.subNodes.Count; i++)
            {
                DomNode dn = (DomNode)this.subNodes[i];
                if (!dn.GenerateConditionJs(data, domXmlNode, action))
                {
                    return false;
                }
            }
            return true;
        }

        public String GenerateEvalJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < subNodes.Count; i++)
            {
                DomNode node = (DomNode)this.subNodes[i];
                String js = node.GenerateEvalJs(data, domXmlNode, action);

                if (js.Length > 0)
                {
                    if (sb.Length > 0)
                    {
                        sb.Append("+");
                    }
                    sb.Append(js);
                }
            }
            return sb.ToString();
        }

        public void AddSubNode(DomNode n)
        {
            this.subNodes.Add(n);
        }

        public ArrayList GetSubNodes()
        {
            return this.subNodes;
        }
    }

    class DomTextNode : DomNode
    {
        String text;

        public DomNodeType GetNodeType()
        {
            return DomNodeType.Text;
        }

        public String EvalueText(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return text;
        }

        public bool GenerateConditionJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return true;
        }

        public String GenerateEvalJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return "\"" + text + "\"";
        }

        public String GetText()
        {
            return text;
        }

        public void SetText(String str)
        {
            this.text = str.Trim();
        }

    }

    class DomVarNode : DomNode
    {
        String varName;

        public DomNodeType GetNodeType()
        {
            return DomNodeType.Var;
        }

        public String EvalueText(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return data.GetField(varName).GetStringValue();
        }

        public bool GenerateConditionJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return true;
        }

        public String GenerateEvalJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {            
            String domId = InfoPathXml.GetNodeAttr(domXmlNode, "domId");
            if (domId == null)
            {
                domId = action.GenerateDomId();
                InfoPathXml.SetNodeAttr(domXmlNode, "domId", domId);
            }
            action.AddOnChangeRelation(this.varName, domId);
            return "getField(\"" + this.varName + "\")";
        }

        public void SetVarName(String n)
        {
            this.varName = n;
        }
    }

    class DomSetAttributeNode : DomNode
    {
        private String attrName;
        private DomNode valueNode = null;

        public DomNodeType GetNodeType()
        {
            return DomNodeType.SetAttribute;
        }

        public bool GenerateConditionJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return this.valueNode.GenerateConditionJs(data, domXmlNode, action);
        }

        public String EvalueText(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            String text = "";
            if (this.valueNode != null)
            {
                text = this.valueNode.EvalueText(data, domXmlNode, action);
            }
            InfoPathXml.SetNodeAttr(domXmlNode, attrName, text);
            return "";
        }

        public String GenerateEvalJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            String domId = InfoPathXml.GetNodeAttr(domXmlNode, "domId");
            if (domId == null)
            {
                domId = action.GenerateDomId();
                InfoPathXml.SetNodeAttr(domXmlNode, "domId", domId);
            }
            String attrDefault = InfoPathXml.GetNodeAttr(domXmlNode, this.attrName);
            String initAttrExp = "";
            if (attrDefault == null)
            {
                initAttrExp = "removeAttribute(\"" + domId + "\", \"" + attrName.Replace(':', '_') + "\")";
            }
            else
            {
                initAttrExp = "setAttribute(\"" + domId + "\", \"" + attrName.Replace(':', '_') + "\",\"" + attrDefault + "\")";
            }
            action.AddAffectAttribute(domId, initAttrExp);
            String value = this.valueNode.GenerateEvalJs(data, domXmlNode, action);
            if (value == null || value.Length == 0)
            {
                value = "\"\"";
            }
            return "setAttribute('" + domId + "', '" + attrName.Replace(':','_') + "', " + value + ")";
        }

        public void SetAttributeName(String name)
        {
            this.attrName = name;
        }

        public String GetAttributeName()
        {
            return this.attrName;
        }

        public void SetValuebNode(DomNode n)
        {
            this.valueNode = n;
        }

        public DomNode GetValueNode()
        {
            return this.valueNode;
        }
    }

    class DomHtmlNode : DomNode
    {
        private XmlNode html;
        private DomNode valueNode = null;

        public DomNodeType GetNodeType()
        {
            return DomNodeType.Html;
        }

        public String EvalueText(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            StringBuilder sb = new StringBuilder();
            String text = null;

            String bindingName = InfoPathXml.GetNodeAttr(this.html, "xd:binding");
            if (bindingName != null && action.NeedOnChange(bindingName))
            {
                String onChangeFunName = "onChange_" + bindingName.Replace(':', '_') + "()";
                InfoPathXml.SetNodeAttr(this.html, "onChange", onChangeFunName);
            }
            if (this.valueNode != null)
            {
                text = this.valueNode.EvalueText(data, this.html, action);
            }

            sb.Append("<").Append(html.Name);
            for (int i = 0; i < html.Attributes.Count; i++)
            {
                XmlAttribute attr = html.Attributes[i];
                sb.Append(" ").Append(attr.Name.Replace(':','_')).Append("=\"").Append(attr.Value).Append("\"");
            }
            sb.Append(">");

            if (this.html.Name.Equals("head"))
            {
                sb.Append("\r\n<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"></meta>");
                sb.Append("\r\n<script type=\"text/javascript\" src=\"jquery-1.8.2.js\"></script>");
                sb.Append("\r\n<script type=\"text/javascript\" src=\"xslt-lib.js\"></script>");
                sb.Append("\r\n<script type=\"text/javascript\" src=\"action.js\"></script>");

            }

            if (text != null)
            {
                sb.Append("\r\n").Append(text).Append("\r\n");
            }
            sb.Append("</").Append(html.Name).Append(">");
            return sb.ToString();
        }

        public bool GenerateConditionJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            return this.valueNode.GenerateConditionJs(data, this.html, action);
        }

        public String GenerateEvalJs(InfoPathData data, XmlNode domXmlNode, XsltActionMap action)
        {
            String js = this.valueNode.GenerateEvalJs(data, this.html, action);
            if (js == null || js.Length == 0)
            {
                return "";
            }
            String domId = InfoPathXml.GetNodeAttr(this.html, "domId");
            if (domId == null)
            {
                return "";
            }

            action.SetEvalueJs(domId, js);
            return "";
        }

        public void SetHtmlNode(XmlNode n)
        {
            this.html = n;
        }

        public XmlNode GetHtmlNode(XmlNode n)
        {
            return this.html;
        }

        public void SetValueNode(DomNode n)
        {
            this.valueNode = n;
        }

        public DomNode GetValueNode()
        {
            return this.valueNode;
        }
    }

    class XsltTemplate
    {
        private DomNode root = null;
        private String htmlText = null;
        private XsltActionMap action = null;

        public XsltTemplate(DomNode n)
        {
            this.root = n;
        }

        public String ToHtml(InfoPathData data)
        {
            if (this.htmlText != null)
            {
                return this.htmlText;
            }
            if (this.action == null)
            {
                this.GetActionMap(data);
            }
            this.htmlText = this.root.EvalueText(data, null, this.action);
            return this.htmlText;
        }

        public XsltActionMap GetActionMap(InfoPathData data)
        {
            if (this.action == null)
            {
                this.action = new XsltActionMap();
                this.root.GenerateEvalJs(data, null, action);
            }
            return action;
        }
        
    }
    
    class XsltDomSet
    {
        private Hashtable templates = new Hashtable();

        public void AddTemplate(String match, XsltTemplate t)
        {
            this.templates.Add(match, t);
        }

        public XsltTemplate GetTemplate(String match)
        {
            return (XsltTemplate)this.templates[match];
        }

        public ArrayList GetAllTemplates()
        {
            ArrayList list = new ArrayList();
            IDictionaryEnumerator i = this.templates.GetEnumerator();
            while (i.MoveNext())
            {
                list.Add(i.Value);
            }
            return list;
        }
    }

    class XsltDomParser
    {
        public XsltDomSet Parse(XmlDocument xd)
        {
            XsltDomSet domSet = new XsltDomSet(); 
            IEnumerator ienum = xd.GetEnumerator();
            while (ienum.MoveNext())
            {
                XmlNode node = (XmlNode)ienum.Current;
                if (node.Name.StartsWith("?"))
                {
                    continue;
                }
                else if (node.Name.Equals("xsl:stylesheet"))
                {
                    for (int i = 0; i < node.ChildNodes.Count; i++)
                    {
                        XmlNode n = node.ChildNodes[i];
                        if (n.Name.Equals("xsl:template"))
                        {
                            String match = n.Attributes["match"].Value;
                            if (match == null || match.Length == 0)
                            {
                                continue;
                            }
                            DomNode templateNode = this.ParseSubNode(n);
                            if (templateNode == null)
                            {
                                continue;
                            }
                            domSet.AddTemplate(match, new XsltTemplate(templateNode));
                        }
                    }
                }
            }
            return domSet;
        }

        private DomNode ParseSubNode(XmlNode node)
        {
            DomGeneralNode domNode = new DomGeneralNode();
            for (int i = 0; i < node.ChildNodes.Count; i++)
            {
                XmlNode n = node.ChildNodes[i];
                if (n.Name.StartsWith("xsl:"))
                {
                    DomNode dn = null;
                    if (n.Name.Equals("xsl:choose"))
                    {
                        dn = this.ParseXslChoose(n);
                    }
                    else if (n.Name.Equals("xsl:if"))
                    {
                        dn = this.ParseXslIf(n);
                    }
                    else if (n.Name.Equals("xsl:attribute"))
                    {
                        dn = this.ParseXslAttribute(n);
                    }
                    else if (n.Name.Equals("xsl:value-of"))
                    {
                        dn = this.ParseXslVauleOf(n);
                    }
                    if (dn != null)
                    {
                        domNode.AddSubNode(dn);
                    }
                }
                else
                {
                    DomNode dn = null;
                    if (n.Name.Equals("#text"))
                    {
                        dn = new DomTextNode();
                        ((DomTextNode)dn).SetText(n.Value);
                    }
                    else if (n.Name.Equals("span") && "PlainText".Equals(InfoPathXml.GetNodeAttr(n, "xd:xctname")))
                    {
                        XmlNode plainTextNode = n.OwnerDocument.CreateNode("element", "input", "");
                        XmlNode plainTextValueNode = n.OwnerDocument.CreateNode("element", "xsl:attribute", "");
                        InfoPathXml.SetNodeAttr(plainTextValueNode, "name", "value");
                        for (int k = 0; k < n.ChildNodes.Count; k++)
                        {
                            if (n.ChildNodes[k].Name.Equals("xsl:attribute"))
                            {
                                plainTextNode.AppendChild(n.ChildNodes[k]);
                            }
                            else
                            {
                                plainTextValueNode.AppendChild(n.ChildNodes[k]);
                            }
                        }
                        InfoPathXml.MoveNodeAttrs(n, plainTextNode);
                        plainTextNode.AppendChild(plainTextValueNode);
                        
                        n.ParentNode.ReplaceChild(plainTextNode, n);

                        dn = new DomHtmlNode();
                        ((DomHtmlNode)dn).SetHtmlNode(plainTextNode);
                        DomNode sub = this.ParseSubNode(plainTextNode);
                        if (sub == null)
                        {
                            continue;
                        }
                        ((DomHtmlNode)dn).SetValueNode(sub);
                    }
                    else
                    {
                        dn = new DomHtmlNode();
                        ((DomHtmlNode)dn).SetHtmlNode(n);
                        DomNode sub = this.ParseSubNode(n);
                        if (sub == null)
                        {
                            continue;
                        }
                        ((DomHtmlNode)dn).SetValueNode(sub);
                    }
                    if (dn != null)
                    {
                        domNode.AddSubNode(dn);
                    }
                }
            }
            if (domNode.GetSubNodes().Count == 1)
            {
                return (DomNode)domNode.GetSubNodes()[0];
            }
            else
            {
                return domNode;
            }
        }

        private DomSetAttributeNode ParseXslAttribute(XmlNode node)
        {
            DomSetAttributeNode domSetAttr = new DomSetAttributeNode();
            String attrName = node.Attributes["name"].Value;
            if (attrName == null || attrName.Length == 0)
            {
                return null;
            }
            domSetAttr.SetAttributeName(attrName);

            DomNode inner = this.ParseSubNode(node);
            if (inner != null)
            {
                domSetAttr.SetValuebNode(inner);
            }
            return domSetAttr;
        }

        private DomVarNode ParseXslVauleOf(XmlNode node)
        {
            DomVarNode domVar = new DomVarNode();
            String varName = node.Attributes["select"].Value;
            if (varName == null || varName.Length == 0)
            {
                return null;
            }
            domVar.SetVarName(varName);
            return domVar;
        }

        private DomConditionNode ParseXslChoose(XmlNode node)
        {
            DomConditionNode domCondition = null;
            DomConditionNode current = null;
            for (int i = 0; i < node.ChildNodes.Count; i++)
            {
                XmlNode n = node.ChildNodes[i];
                if (n.Name.Equals("xsl:when"))
                {
                    DomConditionNode newCondition = new DomConditionNode();
                    String test = n.Attributes["test"].Value;
                    if (test == null || test.Length == 0)
                    {
                        continue;
                    }
                    InfoPathExpressionParser parser = new InfoPathExpressionParser(test);
                    InfoPathExpression exp = parser.Parse();
                    if (exp == null)
                    {
                        continue;
                    }
                    newCondition.SetExp(exp);
                    DomNode branch = this.ParseSubNode(n);
                    newCondition.SetNodes(branch, null);
                    if (current != null)
                    {
                        current.SetFalseNode(newCondition);
                    }
                    current = newCondition;
                    if (domCondition == null)
                    {
                        domCondition = current;
                    }
                }
                else if (n.Name.Equals("xsl:otherwise"))
                {
                    if (current == null)
                    {
                        continue;
                    }
                    DomNode branch = this.ParseSubNode(n);
                    current.SetFalseNode(branch);
                }
            }
            return domCondition;
        }

        private DomConditionNode ParseXslIf(XmlNode node)
        {
            DomConditionNode domCondition = new DomConditionNode();
            String test = InfoPathXml.GetNodeAttr(node, "test");
            if (test == null || test.Length == 0)
            {
                return null;
            }
            InfoPathExpressionParser parser = new InfoPathExpressionParser(test);
            InfoPathExpression exp = parser.Parse();
            if (exp == null)
            {
                return null;
            }
            domCondition.SetExp(exp);

            DomNode sub = this.ParseSubNode(node);
            if (sub == null)
            {
                return null;
            }
            domCondition.SetTrueNode(sub);

            return domCondition;
        }

    }
}
